package token;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class MiniREParser {

	
	public final static String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 
	public final static String NUMBERS = "0123456789";
	
	int cursor = 0;	//This is used to read through a line.
	String statement;
	int lineNumber;
	BTreeNode currentTree;
	BTree currentNode;
	
	//ID: An ID consists of a letter followed by a sequence of UP TO 9 characters.
		//These characters can be either letters, digits, or underscores in any combination.
	private List<String> IDs;
	String ID;
	
	//REGEX: Specification of REGEX is same as  that of project part 1.
	//ASCII-STR: Any string of ASCII printable characters
	
	
	/**LET'S GET STARTED**/
	public BTreeNode parseMiniRE(String statement, int lineNumber)
	{
		//Parse in begin
		//statement_list();
		//Parse in end
		this.statement = statement;
		this.lineNumber = lineNumber;
		this.currentTree = null;
		this.currentNode = null;
		statement();
		cursor = 0;	//Reset the cursor
		return currentTree;
	}
	
//	public void statement_list()	//<statement><statement_list_tail>
//	{
		//statement();	
		//statement_list_tail();
			
//	}
	
	public void statement()
	{
		if (peekToken().equals("ID")) {
			currentTree = new BTreeNode("assignment");
			currentNode = currentTree.getTree();
			currentTree.getTree().addLeft(matchToken("ID"), false);
			peekToken();
			matchToken("=");
			if (peekToken().equals("#")) {
				currentTree.getTree().addRight("numexp", false);
				currentNode = currentNode.getRight();
				matchToken("#");
				peekToken();
				exp();
				matchToken(";");
				return;
			} else if (peekToken().equals("ID") ||
					peekToken().equals("(") || 
					peekToken().equals("find")) {
				currentTree.getTree().addRight("exp", false);
				currentNode = currentNode.getRight();
				exp();
				matchToken(";");
				return;
			} else if (peekToken().equals("maxfreqstring")) {
				currentTree.getTree().addRight("maxfreqstring", false);
				matchToken("maxfreqstring");
				peekToken();
				matchToken("(");
				peekToken();
				currentTree.getTree().getRight().addLeft(matchToken("ID"), false);
				peekToken();
				matchToken(")");
				peekToken();
				matchToken(";");
				return;
			} else {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected assignment statement not found");
			}
		} else if (peekToken().equals("replace")) {
			currentTree = new BTreeNode("replace");
			currentNode = currentTree.getTree();
			matchToken("replace");
			peekToken();
			currentNode.addLeft("regex", false);
			currentNode.getLeft().addLeft(matchToken("REGEX"), false);
			peekToken();
			matchToken("with");
			peekToken();
			currentNode.getLeft().addRight(matchToken("ASCII-STR"), false);
			peekToken();
			matchToken("in");
			peekToken();
			currentNode.addRight("files", false);
			file_names();
			matchToken(";");
			return;
			
		} else if (peekToken().equals("recursivereplace")) {
			currentTree = new BTreeNode("recursivereplace");
			currentNode = currentTree.getTree();
			matchToken("recursivereplace");
			peekToken();
			currentNode.addLeft("regex", false);
			currentNode.getLeft().addLeft(matchToken("REGEX"), false);
			peekToken();
			matchToken("with");
			peekToken();
			currentNode.getLeft().addRight(matchToken("ASCII-STR"), false);
			peekToken();
			matchToken("in");
			peekToken();
			currentNode.addRight("files", false);
			file_names();
			matchToken(";");
			return;
			
		} else if (peekToken().equals("print")) {
			currentTree = new BTreeNode("print");
			currentNode = currentTree.getTree();
			matchToken("print");
			matchToken("(");
			exp_list();
			matchToken(")");
			matchToken(";");
			return;
		} else {
			throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": No valid statement found");
		}
		
		//ID = exp();
		//ID = "#"+exp();
		//ID = maxfreqstring(ID);
		//ID = <exp>
		//ID = #<exp>
		//ID = maxfreqstring(ID)

		//replace REGEX with ASCII-STR in <file-names>
		//recursiveplace REGEX with ASCII-STR in <file-names>
		
		//print(exp-list>
	}
	
/*	public void statement_list_tail()	//<statement><statement_list_tail?|Epsilon
	{
		
		//if this is not epsilon, then it's the two.
		statement(statement);
		statement_list_tail();
	}
*/	
	public void file_names()
	{
		source_file();
		matchToken(">!");
		peekToken();
		destination_file();
	}
	
	public void source_file()
	{
		currentNode.getRight().addLeft(matchToken("ASCII-STR"), false);
		peekToken();
	}
	
	public void destination_file()
	{
		currentNode.getRight().addRight(matchToken("ASCII-STR"), false);
		peekToken();
	}
	
	
	
	public void exp_list()
	{
		currentNode.addLeft("exp", false);
		currentNode = currentNode.getLeft();
		exp();
		currentNode = currentTree.getTree();
		currentNode.addRight("explisttail", false);
		currentNode = currentNode.getRight();
		exp_list_tail();
	}
	
	public void exp()
	{
		if (peekToken().equals("ID")) {
			currentNode.addLeft(matchToken("ID"), false);
			return;
		} else if (peekToken().equals("(")) {
			matchToken("(");
			exp();
			matchToken(")");
			return;
			
		} else if (peekToken().equals("find")) {
			currentNode.addLeft("term", false);
			term();
			currentNode.addRight("exptail", false);
			exp_tail();
			return;
		} else {
			throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expression is invalid");
		}
	}
	
	public void exp_list_tail()
	{
		if (peekToken().equals(",")) {
			BTree tempNode = currentNode;
			matchToken(",");
			currentNode.addLeft("exp", false);
			currentNode = currentNode.getLeft();
			exp();
			currentNode = tempNode;
			currentNode.addRight("explisttail", false);
			currentNode = currentNode.getRight();
			exp_list_tail();
			return;
		} else {
			return;
		}
	}

	public void exp_tail()
	{
		if (peekToken().equals("diff") ||
				peekToken().equals("union") ||
				peekToken().equals("inters")) {
			currentNode = currentNode.getRight();
			bin_op();
			term();
			currentNode.addRight("exptail", false);
			exp_tail();
			return;
		} else {
			//Not sure if this is a good idea...
			currentNode.getRight().addLeft("empty", false);
			return;
		}
	}
	
	public void bin_op()
	{
		if (peekToken().equals("diff")) {
			currentNode.addLeft("diff", false);
			matchToken("diff");
			peekToken();
			return;
		} else if (peekToken().equals("union")) {
			currentNode.addLeft("union", false);
			matchToken("union");
			peekToken();
			return;
		} else if (peekToken().equals("inters")) {
			currentNode.addLeft("inters", false);
			matchToken("inters");
			peekToken();
			return;
		} else {
			throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Invalid binary operator");
		}
	}
	
	public void term()
	{
		matchToken("find");
		peekToken();
		currentNode.getLeft().addLeft(matchToken("REGEX"), false);
		peekToken();
		matchToken("in");
		peekToken();
		file_name();
	}
	
	public void file_name()
	{
		currentNode.getLeft().addRight(matchToken("ASCII-STR"), false);
		
	}
	
	public String peekToken() throws IllegalStateException {
	if (statement.length() - cursor >= 7 && 
		statement.substring(cursor, cursor + 7).equals("replace") &&
			statement.charAt(cursor + 7) == ' ') {
		return "replace";
	}
	if (statement.length() - cursor >= 16 &&
			statement.substring(cursor, cursor + 16).equals("recursivereplace") &&
			statement.charAt(cursor + 16) == ' ') {
		return "recursivereplace";
	}
	if (statement.length() - cursor >= 5 &&
			statement.substring(cursor, cursor + 5).equals("begin")) {
		return "begin";
	}
	if (statement.length() - cursor >= 3 &&
			statement.substring(cursor, cursor + 3).equals("end")) {
		return "end";
	}
	if (statement.length() - cursor >= 1 &&
			statement.substring(cursor, cursor + 1).equals("=") && 
			statement.charAt(cursor + 1) == ' ') {
		return "=";
	}
	if (statement.length() - cursor >= 4 &&
			statement.substring(cursor, cursor + 4).equals("with") &&
			statement.charAt(cursor + 4) == ' ') {
		return "with";
	}
	if (statement.length() - cursor >= 2 &&
			statement.substring(cursor, cursor + 2).equals("in") &&
			statement.charAt(cursor + 2) == ' ') {
		return "in";
	}
	if (statement.length() - cursor >= 1 &&
			statement.substring(cursor, cursor + 1).equals(";")) {
		return ";";
	}
	if (statement.length() - cursor >= 2 &&
			statement.substring(cursor, cursor + 2).equals(">!") &&
			statement.charAt(cursor + 2) == ' ') {
		return ">!";
	}
	if (statement.length() - cursor >= 5 &&
			statement.substring(cursor, cursor + 5).equals("print") &&
			(statement.charAt(cursor + 5) == ' ' ||
			statement.charAt(cursor + 5) == '(')) {
		return "print";
	}
	if (statement.length() - cursor >= 1 &&
			statement.substring(cursor, cursor + 1).equals("(")) {
		return "(";
	}
	if (statement.length() - cursor >= 1 &&
			statement.substring(cursor, cursor + 1).equals(")")) {
		return ")";
	}
	if (statement.length() - cursor >= 1 &&
			statement.substring(cursor, cursor + 1).equals(",")) {
		return ",";
	}
	if (statement.length() - cursor >= 1 &&
			statement.substring(cursor, cursor + 1).equals("#")) {
		return "#";
	}
	if (statement.length() - cursor >= 4 &&
			statement.substring(cursor, cursor + 4).equals("find") &&
			statement.charAt(cursor + 4) == ' ') {
		return "find";
	}
	if (statement.length() - cursor >= 4 &&
			statement.substring(cursor, cursor + 4).equals("diff") &&
			statement.charAt(cursor + 4) == ' ') {
		return "diff";
	}
	if (statement.length() - cursor >= 5 &&
			statement.substring(cursor, cursor + 5).equals("union") &&
			statement.charAt(cursor + 5) == ' ') {
		return "union";
	}
	if (statement.length() - cursor >= 6 &&
			statement.substring(cursor, cursor + 6).equals("inters") &&
			statement.charAt(cursor + 6) == ' ') {
		return "inters";
	}
	if (statement.length() - cursor >= 13 &&
			statement.substring(cursor, cursor + 13).equals("maxfreqstring") &&
			(statement.charAt(cursor + 13) == ' ' ||
			statement.charAt(cursor + 13) == '(')) {
		return "maxfreqstring";
	}
	if (ALPHABET.contains(Character.toString(statement.charAt(cursor)))) {
		return "ID";
	}
	if (statement.charAt(cursor) == ' ') {
		cursor++;
		return peekToken();
	}
	
	if (statement.charAt(cursor) == '\'' ||
			statement.charAt(cursor) == '"') {
		return "STRING";
	}
	
	throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": invalid token was found");
	}
	
	public String matchToken(String token) throws IllegalStateException {
		
		//IF: Token is an ID, then we need to grab the ID.
		if (token.equals("ID")) {
			String identifier = "";
			//While the character pointed to by cursor is not a ' ' -or- a ','
			//which means it is a viable character, we have to concat it for the identifier to
			//determine the ID.
			while(statement.charAt(cursor) != ' ' &&
					statement.charAt(cursor) != ',' &&
					statement.charAt(cursor) != ')' &&
					statement.charAt(cursor) != ';') {
				//Fail case.
				if (!ALPHABET.contains(Character.toString(statement.charAt(cursor))) &&
						!NUMBERS.contains(Character.toString(statement.charAt(cursor))) &&
						statement.charAt(cursor) != '_') {
					throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": invalid variable identifier was found");
				}
				identifier = identifier.concat(Character.toString(statement.charAt(cursor)));
				cursor++;
			}
			//We have broken the conditionals, so step forward once.
			return identifier;
			
		}
		
		//IF: token is an "=", then set the cursor two steps ahead.
		if (token.equals("=")) {
			if (statement.charAt(cursor) != '=') {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected '='");
			}
			cursor++;
		}
		//IF: token is an "#", then set the cursor two steps ahead.
		if (token.equals("#")) {
			if (statement.charAt(cursor) != '#') {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected '#'");
			}
			cursor++;
		}
		//IF: token is a ";", then set cursor two steps ahead.
		if (token.equals(";")) {
			if (statement.charAt(cursor) != ';') {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected ';'");
			}
			cursor++;
		}
		//IF: token is a "find", then set cursor ahead of find plus a step.  This is equivalent to find' '
		if (token.equals("find")) {
			if (!statement.substring(cursor, cursor + 4).equals("find")) {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected \"find\"");
			}
			cursor = cursor + 4;
		}
		//IF: token is "in", then set cursor three steps ahead (in' ')
		if (token.equals("in")) {
			if (!statement.substring(cursor, cursor + 2).equals("in")) {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected \"in\"");
			}
			cursor = cursor + 2;
		}
		//IF: token is a "(", then set cursor ahead one step into parentheses
		if (token.equals("(")) {
			if (statement.charAt(cursor) != '(') {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected '('");
			}
			cursor++;
		}
		//IF: token is a ")", set cursor a step ahead.
		if (token.equals(")")) {
			if (statement.charAt(cursor) != ')') {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected ')'");
			}
			cursor++;
		}
		if (token.equals("REGEX")) {
			String regex = "";
			if (statement.charAt(cursor) != '\'') {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected '\''");
			}
			cursor++;
			while (statement.charAt(cursor) != '\''){
				if (statement.charAt(cursor) == '\\'
						&& statement.charAt(cursor + 1) == '\'') {
					regex.concat(statement.substring(cursor, cursor + 2));
					cursor = cursor + 2;
					continue;
				}
				regex = regex.concat(Character.toString(statement.charAt(cursor)));
				cursor++;
			}
			cursor++;
			return regex;
		}
		
		if (token.equals("ASCII-STR")) {
			String ascii = "";
			if (statement.charAt(cursor) != '"') {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected '\"'");
			}
			cursor++;

			while (statement.charAt(cursor) != '"') {
				if ((int) statement.charAt(cursor) < 32 ||
						(int) statement.charAt(cursor) > 126) {
					throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Invalid ASCII string");
				}
				if (statement.charAt(cursor) == '\\' &&
						statement.charAt(cursor + 1) == '"') {
					ascii.concat(statement.substring(cursor, cursor + 2));
					cursor = cursor + 2;
					continue;
				}
				ascii = ascii.concat(Character.toString(statement.charAt(cursor)));
				cursor++;
			}
			cursor++;
			return ascii;
		}
		if (token.equals("diff")) {
			if (!statement.substring(cursor, cursor + 4).equals("diff")) {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected \"diff\"");
			}
			cursor = cursor + 4;
		}
		if (token.equals("union")) {
			if (!statement.substring(cursor, cursor + 5).equals("union")) {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected \"union\"");
			}
			cursor = cursor + 5;
		}
		if (token.equals("inters")) {
			if (!statement.substring(cursor, cursor + 6).equals("inters")) {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected \"inters\"");
			}
			cursor = cursor + 6;
		}
		if (token.equals("replace")) {
			if (!statement.substring(cursor, cursor + 7).equals("replace")) {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected \"replace\"");
			}
			cursor = cursor + 7;
		}
		if (token.equals("recursivereplace")) {
			if (!statement.substring(cursor, cursor + 16).equals("recursivereplace")) {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected \"recursivereplace\"");		
			}
			cursor = cursor + 16;
		}
		if (token.equals("with")) {
			if (!statement.substring(cursor, cursor + 4).equals("with")) {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected \"with\"");
			}
			cursor = cursor + 4;
		}
		if (token.equals(">!")) {
			if (!statement.substring(cursor, cursor + 2).equals(">!")) {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected \">!\"");
			}
			cursor = cursor + 2;
		}
		if (token.equals("print")) {
			if (!statement.substring(cursor, cursor + 5).equals("print")) {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected \"print\"");
			}
			cursor = cursor + 5;
		}
		if (token.equals(",")) {
			if (!statement.substring(cursor, cursor + 1).equals(",")) {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected ','");
			}
			cursor = cursor + 1;
		}
		if (token.equals("maxfreqstring")) {
			if (!statement.substring(cursor, cursor + 13).equals("maxfreqstring")) {
				throw new IllegalStateException("ERROR: Line " + Integer.toString(lineNumber) + ": Expected \"maxfreqstring\"");
			}
			cursor = cursor + 13;
		}
		
		
		return "";
	}
}

